/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package imagemanip.structures;

import fr.univnantes.alma.arbres.Arbre.ArbreIteratorType;
import fr.univnantes.alma.arbres.Noeud;
import fr.univnantes.alma.arbres.affichage.AffichageLA;
import fr.univnantes.alma.arbres.affichage.AffichagePSTree;
import fr.univnantes.alma.arbres.exceptions.PereRacineException;
import fr.univnantes.alma.arbres.exceptions.PlaceIndisponibleException;
import java.awt.Color;
import java.util.Collections;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.LinkedList;

/**
 *
 * @author clotilde
 */
public class OcTreeManager {

    private OcTree ot = new OcTree();

    public OcTreeManager() {
    }

    public OcTree getOt() {
        return ot;
    }

    public void setOt(OcTree ot) {
        this.ot = ot;
    }



    /*********** CONSTRUCTION PALETTE ***********/

    /**
     * Importe toute une palette de couleurs dans un OcTree
     * @param pixels
     */
    public void importPaletteImage(Color[][] pixels) {
        for (int x = 0; x < pixels.length; ++x) {
            for (int y = 0; y < pixels[0].length; ++y) {
                try {

//                    System.out.println("===> Couleur : [" + x + " , "+y+ "]");

//                    //System.out.println("===> Couleur : [" + x + " , "+y+ "]");

                    addCouleur(pixels[x][y]);
                } catch (NullPointerException ex) {
                    Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
                } catch (PlaceIndisponibleException ex) {
                    Logger.getLogger(QuadTreeManager.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        System.out.println((new AffichagePSTree<OcTreeElement>(this.ot)).toString());
    }

    /**
     * Ajoute une couleur dans l'arbre OcTree
     * @param c : couleur à ajouter
     * @throws NullPointerException
     * @throws PlaceIndisponibleException
     */
    private void addCouleur(Color c) throws NullPointerException, PlaceIndisponibleException {
        String rouge = Integer.toBinaryString(c.getRed());
        String vert = Integer.toBinaryString(c.getGreen());
        String bleu = Integer.toBinaryString(c.getBlue());

        //On les met à la même taille
        while (rouge.length() < ot.getArite()) {
            rouge = "0".concat(rouge);
        }

        while (vert.length() < ot.getArite()) {
            vert = "0".concat(vert);
        }

        while (bleu.length() < ot.getArite()) {
            bleu = "0".concat(bleu);
        }
/*
//System.out.println("Rouge : " + rouge);
//System.out.println("Vert : " + vert);
//System.out.println("Bleu : " + bleu);
*/

        Noeud<OcTreeElement> pere = this.ot.getRacine();

        for (int i = 0; i < rouge.length(); ++i) {
            int num = Integer.parseInt(String.valueOf(rouge.charAt(i)).concat(String.valueOf(vert.charAt(i))).concat(String.valueOf(bleu.charAt(i))), 2);
/*
int pos = num+1;
//System.out.println("pos : "+ pos);
*/
            Noeud<OcTreeElement> fils = ot.getFilsByPos(pere, num + 1);

            OcTreeElement value = null;

            if (fils == null) {
                if (i == rouge.length() - 1) {
                    //On ajoute la couleur avec la feuille dans "value"
                    value = new OcTreeElement(c, 1);
                }
                fils = ot.ajoutFilsEnPos(pere, num + 1, value);
            } else {
                if (i == rouge.length() - 1) {
                    // Si la couleur est déjà présente, on change juste son nombre d'occurrences
                    fils.getValeur().setOccurrence(fils.getValeur().getOccurence() + 1);
                }
            }
/*
if (fils.getValeur() != null){
int occ = fils.getValeur().getOccurence();
//System.out.println("Occ : " + occ);
}*/
            pere = fils;

        }
    }



    /*********** COMPRESSION ***********/

    /**
     * Compresse les couleurs d'une palette qui ont un pourcentage de présence inférieur à un paramètre donné
     * @param n : pourcentage à ne pas dépasser
     */
    public void reductionPaletteParPourcentage(int n) throws PereRacineException, PlaceIndisponibleException {
        //System.out.println("Début reductionPal");

        LinkedList<PereOcFils> sousFeuilles  = rechercheSousFeuilles();
        int nbPixels = nombrePixels();

//System.out.println("Sous feuilles : "+sousFeuilles.toString());
//System.out.println("Nbpixels : "+nbPixels);


        for (int i=0; i < sousFeuilles.size(); i++) {

//System.out.println("NbOcc de "+sousFeuilles.get(i).getPere().toString()+" : "+sousFeuilles.get(i).getOccurrences());
//System.out.println("NbFils : "+listeFils(sousFeuilles.get(i).getPere()).size());

            if ((((double)sousFeuilles.get(i).getOccurrences()) / (double) nbPixels)*100 < n) {
                // Si on a un nombre d'occurrences plus petit que le nombre donné, on fusionne
                fusionCouleurs(sousFeuilles.get(i).getPere());
            }
        }
//System.out.println("Fin de reducPPP");
    }
    
    /**
     * Fusionne les fils d'un noeud (qui seront supprimés)
     * @param pere : père d'une fratrie à fusionner, qui deviendra donc une feuille
     */
    private void fusionCouleurs(Noeud<OcTreeElement> pere) throws PereRacineException, PlaceIndisponibleException {
//System.out.println("Début fusionCouleurs");

        LinkedList<Noeud<OcTreeElement>> fratrie = listeFils(pere);
//System.out.println(fratrie.toString());
        LinkedList<OcTreeElement> couleurs = listeOcTreeElement(fratrie);

//System.out.println("Taille fratrie : "+ fratrie.size());
//System.out.println("Taille couleurs : "+ couleurs.size());

        OcTreeElement valeur = null;

        // Changement valeur du père :
        if (fratrie.size() == 1) {
            valeur = couleurs.getFirst();
        }
        else {
            valeur = ColorEditor.moyenneListeColorsPonderation(couleurs);
        }

        // Le père devient la nouvelle feuille :
        int pos = pere.getPosition(); //On retient la position du père
        Noeud<OcTreeElement> grandpere = this.ot.getPere(pere); // On retient le père du père
        this.ot.deleteNoeud(pere); // On supprime le père...
        this.ot.ajoutFilsEnPos(grandpere, pos, valeur); // Pour le recréer, avec la bonne valeur !


////System.out.println((new AffichageLA(this.getOt(),ArbreIteratorType.DFS)).toString());
    }

    /**
     * Compresse une palette selon un nombre de couleurs : réduire la palettte au nombre de couleurs donné
     * @param n : nombre de couleurs voulues au final
     */
    private void reductionPaletteParNbCouleurs(int n) {
        if (n<nombreCouleurs()) { //Si on veut un nombre de couleurs supérieur, ben... on fait rien.
            
        }
    }
    // PAS FAIT :D


    /*********** AIDES ***********/

    /**
     * Récupère la liste des sous-feuilles avec le nombre d'occurences de leurs fils
     * @return une liste de PereOcFils (couple de Noeud et Int)
     */
    private LinkedList<PereOcFils> rechercheSousFeuilles(){
        //System.out.println("Début recherchSousFeuilles");
       LinkedList<PereOcFils> feuilles = new LinkedList<PereOcFils>();
       Iterator<Noeud<OcTreeElement>> it = ot.iteratorDFS();
       Noeud<OcTreeElement> p = null;
       Noeud<OcTreeElement> n = null;

       while(it.hasNext()){
           p=n;
           n = it.next();
           if (n.getValeur() !=null) {
               PereOcFils fratrie = new PereOcFils(p,0);
               while (it.hasNext() && n.getValeur() !=null) { //Remplir le type PereOcFils
                   fratrie.setOccurrences(fratrie.getOccurrences() + n.getValeur().getOccurence());
                   n = it.next();
                }
               if (n.getValeur() !=null) {
                   fratrie.setOccurrences(fratrie.getOccurrences() + n.getValeur().getOccurence());
               }
               feuilles.add(fratrie);
           }
       }
       Collections.sort(feuilles);
       //System.out.println("fin recherche sous feuilles");
       return(feuilles);
    }

    /**
     * Permet d'avoir la liste des fils d'un noeud
     * @param pere : Noeud père dont on veut la liste des fils
     * @return une liste des fils du noeud donné
     */
    private LinkedList<Noeud<OcTreeElement>> listeFils(Noeud<OcTreeElement> pere) {
        //System.out.println("Début listeFils");


        if(this.getOt().hasFils(pere)){
            LinkedList<Noeud<OcTreeElement>> ll = new LinkedList<Noeud<OcTreeElement>>();
            Noeud<OcTreeElement> fils = this.getOt().getFirstFils(pere);

            ll.add(fils);
            while(this.getOt().hasNextFrere(fils)){
                fils = this.getOt().getNextFrere(fils);
                ll.add(fils);
            }
            //System.out.println("fin listeFils");
            return ll;
        }
        //System.out.println("fin listeFils");
        return null;
    }

    /**
     * Renvoie une liste d'OcTreeElement à partir d'une liste de Noeuds<OcTreeElement>
     * @param fratrie
     * @return
     */
    private LinkedList<OcTreeElement> listeOcTreeElement(LinkedList<Noeud<OcTreeElement>> fratrie) {
                //System.out.println("Début listeOTE");

        LinkedList<OcTreeElement> couleurs = new LinkedList<OcTreeElement>();
        for (int i = 0; i<fratrie.size();i++) {
            couleurs.add(fratrie.get(i).getValeur());
        }
        return couleurs;
    }

    /**
     * Compte le nombre de pixels présents dans l'image
     * @return un entier (nombre de pixels)
     */
    private int nombrePixels() {
        int pixels = 0;
        Iterator<Noeud<OcTreeElement>> it = ot.iteratorBFS();

        while(it.hasNext()) {
            Noeud<OcTreeElement> n = it.next();
            if(n.getValeur() != null){
                pixels = pixels + n.getValeur().getOccurence();
            }
        }
        return(pixels);
    }

    /**
     * Compte le nombre de couleurs présentes dans l'image
     * @return un entier (nombre de couleurs)
     */
    private int nombreCouleurs() {
        int nbCol = 0;
        Iterator<Noeud<OcTreeElement>> it = ot.iteratorBFS();

        while(it.hasNext()) {
            Noeud<OcTreeElement> n = it.next();
            if(n.getValeur() != null){
                nbCol++;
            }
        }
        return(nbCol);
    }

     /**
     * Recherche toutes les feuilles d'un OcTree
     * @return une liste des feuilles
     */
    public LinkedList<Noeud<OcTreeElement>> rechercheFeuilles() {
        LinkedList<Noeud<OcTreeElement>> feuilles = new LinkedList<Noeud<OcTreeElement>>();
        Iterator<Noeud<OcTreeElement>> it = this.ot.iteratorBFS();

        while(it.hasNext()){
            Noeud<OcTreeElement> n = it.next();
            if(n.getValeur() != null){
                feuilles.add(n);
            }
        }
        return (feuilles);
    }




    /*********** RECUPERATION COULEURS ***********/

   /**
     * Récupère la couleur à laquelle correspond la couleur passée en paramètre
     * (Si cette couleur existe, ok. Si palette compressée, renvoyer la nouvelle couleur de remplacement. Si profondeur = 9 et que la couleur n'existe pas, renvoyer la couleur frère la plus présente.)
     * @param c : Couleur
     * @return nouvelle couleur de remplacement
     */
    public Color recupererCouleur(Color c) {
//System.out.println("début recupCoul");
        String rouge = Integer.toBinaryString(c.getRed());
        String vert = Integer.toBinaryString(c.getGreen());
        String bleu = Integer.toBinaryString(c.getBlue());

        //On les met à la même taille
        while (rouge.length() < ot.getArite()) {
            rouge = "0".concat(rouge);
        }

        while (vert.length() < ot.getArite()) {
            vert = "0".concat(vert);
        }

        while (bleu.length() < ot.getArite()) {
            bleu = "0".concat(bleu);
        }

        Color couleur = null;
        Noeud<OcTreeElement> noeudCourant = this.ot.getRacine();

////System.out.println(rouge.length());

        for (int i = 0; i < rouge.length(); ++i) {
////System.out.println("i = "+i);
            int num = Integer.parseInt(String.valueOf(rouge.charAt(i)).concat(String.valueOf(vert.charAt(i))).concat(String.valueOf(bleu.charAt(i))), 2);
////System.out.println(num);
            if (noeudCourant.getValeur() != null) {
            //Chemin existant
                //Si on tombe sur une feuille, on a la bonne couleur
////System.out.println("trouvé une valeur");
                couleur = noeudCourant.getValeur().getCouleur();
            } else {
////System.out.println(noeudCourant.toString() + " pas une feuille");
                if (this.ot.getFilsByPos(noeudCourant, num + 1) != null) {
////System.out.println("y'a encore un fils sur le chemin");
                //Si on est bien sur le bon chemin mais qu'on n'est pas encore arrivé à une feuille (y a encore des fils)
                    noeudCourant = this.ot.getFilsByPos(noeudCourant, num + 1);
                } else {
////System.out.println("pas trouvé, on renvoie la plus courte");
            // Chemin inexistant : renvoyer la couleur la plus proche parmi toutes les couleurs 
                 LinkedList<OcTreeElement> couleurs = listeOcTreeElement(this.rechercheFeuilles());
                 couleur = ColorEditor.colorMinDist(c, couleurs);
                }
            }
        }
        if(noeudCourant.getValeur() != null){
////System.out.println("trouvé une valeur à la fin");
            couleur = noeudCourant.getValeur().getCouleur();
        }
        return (couleur);
    }



}
